package main

import (
	"container/heap"
	"time"
)

func NewPriorityQueue() *PriorityQueue {
	q := &PriorityQueue{
		isPushing: false,
		ch:        make(chan *QueueItem, 1),
	}
	return q
}

type PriorityQueue struct {
	//队列(最小堆)
	items     []*QueueItem    //堆
	ch        chan *QueueItem //同步控制
	isPushing bool            //推送状态
}

// 数据项
type QueueItem struct {
	Value    interface{} // value
	Priority int         // 优先级(越小越靠前)
	JoinTime time.Time   // 加入时间
	index    int         // 索引
}

func (pq PriorityQueue) Len() int { return len(pq.items) }

func (pq PriorityQueue) Less(i, j int) bool {
	// priority越小越靠前
	if pq.items[i].Priority != pq.items[j].Priority {
		return pq.items[i].Priority < pq.items[j].Priority
	} else {
		return pq.items[i].JoinTime.Sub(pq.items[j].JoinTime) < 0
	}
}

func (pq PriorityQueue) Swap(i, j int) {
	pq.items[i], pq.items[j] = pq.items[j], pq.items[i]
	pq.items[i].index = i
	pq.items[j].index = j
}

func (pq *PriorityQueue) Push(x interface{}) {
	n := len(pq.items)
	item := x.(*QueueItem)
	item.index = n
	pq.items = append(pq.items, item)
}

func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old.items)
	if n == 0 {
		return nil
	}
	item := old.items[n-1]
	old.items[n-1] = nil // avoid memory leak
	item.index = -1      // for safety
	pq.items = old.items[0 : n-1]
	return item
}

// update modifies the priority and value of an Item in the queue.
func (pq *PriorityQueue) update(item *QueueItem, value interface{}, priority int) {
	item.Value = value
	item.Priority = priority
	heap.Fix(pq, item.index)
}

func (pq *PriorityQueue) Get() *QueueItem {
	result := *<-pq.ch
	return &result
}

func (pq *PriorityQueue) PutNoWait(item *QueueItem) {
	heap.Push(pq, item)
	//启用推送
	pq.startPushing()
}

//负责推送数据
func (pq *PriorityQueue) startPushing() {
	if pq.isPushing {
		return
	}
	go pq._startPushing()
}

func (pq *PriorityQueue) _startPushing() {
	for pq.Len() > 0 {
		pq.isPushing = true

		x := heap.Pop(pq)
		if x == nil {
			continue
		}
		item := x.(*QueueItem)
		pq.ch <- item
	}
	pq.isPushing = false
}
